package com.shadowvc.sdk;

import com.shadowvc.sdk.exception.ApiException;
import com.shadowvc.sdk.exception.ApiRuleException;
import com.shadowvc.sdk.internal.parser.JsonParser;
import com.shadowvc.sdk.internal.util.*;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

/**
 * 连接客户端
 * <p>
 * File: DefaultApiClient.java
 * Description:
 * <p>
 * Copyright: Copyright (c) 2012 ecbox.com
 * Company: ECBOX,Inc.
 *
 * @author chenxiaochun
 * @version 1.0
 */
public class DefaultApiClient implements ApiClient {

  private static final String APP_KEY = "appKey"; // v
  private static final String FORMAT = "format";
  private static final String METHOD = "method"; // v
  private static final String TIMESTAMP = "timestamp"; // v
  private static final String VERSION = "v";
  private static final String SIGN = "sign"; // v
  private static final String SIGN_METHOD = "signMethod";
  private static final String SDK_VERSION = "sdkVersion";
  private static final String ACCESS_KEY = "accessKey";

  private String serverUrl;
  private String appKey;
  private String appSecret;
  private String format = Constants.FORMAT_JSON;
  private String signMethod = Constants.SIGN_METHOD_MD5;

  private int connectTimeout = 3000;// 3秒
  private int readTimeout = 15000;// 15秒

  private boolean needCheckRequest = true;
  private boolean needEnableParser = true;

  public DefaultApiClient(String serverUrl, String appKey, String appSecret) {
    this.appKey = appKey;
    this.appSecret = appSecret;
    this.serverUrl = serverUrl;
  }

  public DefaultApiClient(String serverUrl, String appKey, String appSecret, String format) {
    this(serverUrl, appKey, appSecret);
    this.format = format;
  }

  public DefaultApiClient(String serverUrl, String appKey, String appSecret, String format, int connectTimeout,
                          int readTimeout) {
    this(serverUrl, appKey, appSecret, format);
    this.connectTimeout = connectTimeout;
    this.readTimeout = readTimeout;
  }

  public DefaultApiClient(String serverUrl, String appKey, String appSecret, String format, int connectTimeout,
                          int readTimeout, String signMethod) {
    this(serverUrl, appKey, appSecret, format, connectTimeout, readTimeout);
    this.signMethod = signMethod;
  }

  @Override
  public <T extends ApiResponse> T execute(ApiRequest<T> request) throws ApiException {
    return execute(request, null);
  }

  @Override
  public <T extends ApiResponse> T execute(ApiRequest<T> request, String accessKey) throws ApiException {
    ApiParser<T> parser = null;
    if (this.needEnableParser) {
      parser = new JsonParser<T>(request.getResponseClass());
    }
    return _execute(request, parser, accessKey);
  }

  private <T extends ApiResponse> T _execute(ApiRequest<T> request, ApiParser<T> parser, String accessKey)
          throws ApiException {
    if (this.needCheckRequest) {
      try {
        request.check();// if check failed,will throw ApiRuleException.
      } catch (ApiRuleException e) {
        T localResponse = null;
        try {
          localResponse = request.getResponseClass().newInstance();
        } catch (InstantiationException e2) {
          throw new ApiException(e2);
        } catch (IllegalAccessException e3) {
          throw new ApiException(e3);
        }
        localResponse.setErrorCode(e.getErrCode());
        localResponse.setMsg(e.getErrMsg());
        return localResponse;
      }
    }

    Map<String, Object> rt = doPost(request, accessKey);
    if (rt == null)
      return null;

    T tRsp = null;
    if (this.needEnableParser) {
      try {
        tRsp = parser.parse((String) rt.get("rsp"));
        tRsp.setResponseBody((String) rt.get("rsp"));
      } catch (RuntimeException e) {
        ApiLogger.logBizError((String) rt.get("rsp"));
        throw e;
      }
    } else {
      try {
        tRsp = request.getResponseClass().newInstance();
        tRsp.setResponseBody((String) rt.get("rsp"));
      } catch (Exception e) {
      }
    }

    tRsp.setRequestParams((ApiHashMap) rt.get("textParams"));
    tRsp.setRequestUrl((String) rt.get("url"));
    if (!tRsp.isSuccess()) {
      ApiLogger.logErrorScene(rt, tRsp, appSecret);
    }
    return tRsp;
  }

  public <T extends ApiResponse> Map<String, Object> doPost(ApiRequest<T> request, String accessKey)
          throws ApiException {
    Map<String, Object> result = new HashMap<String, Object>();

    // 请求参数容器
    RequestParamHolder requestHolder = new RequestParamHolder();
    ApiHashMap appParams = new ApiHashMap(request.getTextParams());
    requestHolder.setAppParams(appParams);

    // 添加协议级请求参数
    ApiHashMap proMustParams = new ApiHashMap();
    proMustParams.put(METHOD, request.getApiMethodName());
    proMustParams.put(VERSION, "2.0");
    proMustParams.put(APP_KEY, appKey);
    Long timestamp = request.getTimestamp();// 允许用户设置时间戳
    if (timestamp == null) {
      timestamp = System.currentTimeMillis();
    }

    // 因为沙箱目前只支持时间字符串，所以暂时用Date格式
    DateFormat df = new SimpleDateFormat(Constants.DATE_TIME_FORMAT);
    df.setTimeZone(TimeZone.getTimeZone(Constants.DATE_TIMEZONE));
    proMustParams.put(TIMESTAMP, df.format(new Date(timestamp)));//
    requestHolder.setProMustParams(proMustParams);

    // 协议可选参数
    ApiHashMap proOptParams = new ApiHashMap();
    proOptParams.put(FORMAT, format);// 格式
    proOptParams.put(SIGN_METHOD, signMethod);// 签名方法
    proOptParams.put(ACCESS_KEY, accessKey);// 访问令牌
    proOptParams.put(SDK_VERSION, Constants.SDK_VERSION);// sdk版本
    requestHolder.setProOptParams(proOptParams);

    // 添加签名参数
    try {
      if (Constants.SIGN_METHOD_MD5.equals(signMethod)) {
        // 注意签名放到了协议必填参数中
        proMustParams.put(SIGN, ApiUtils.signEopRequestNew(requestHolder, appSecret, false));
      } else if (Constants.SIGN_METHOD_HMAC.equals(signMethod)) {
        proMustParams.put(SIGN, ApiUtils.signEopRequestNew(requestHolder, appSecret, true));
      } else {
        proMustParams.put(SIGN, ApiUtils.signEopRequest(requestHolder, appSecret));
      }
    } catch (IOException e) {
      throw new ApiException(e);
    }

    StringBuffer urlSb = new StringBuffer(serverUrl);
    try {
      String sysMustQuery = WebUtils.buildQuery(requestHolder.getProMustParams(), Constants.CHARSET_UTF8);
      String sysOptQuery = WebUtils.buildQuery(requestHolder.getProOptParams(), Constants.CHARSET_UTF8);

      urlSb.append("?");
      urlSb.append(sysMustQuery);
      //if (sysOptQuery != null & sysOptQuery.length() > 0) {
      if (sysOptQuery != null) {
        urlSb.append("&");
        urlSb.append(sysOptQuery);
      }
    } catch (IOException e) {
      throw new ApiException(e);
    }

    String rsp = null;
    try {
      // 是否需要上传文件
      if (request instanceof ApiUploadRequest) {
        ApiUploadRequest<T> uRequest = (ApiUploadRequest<T>) request;
        Map<String, FileItem> fileParams = ApiUtils.cleanupMap(uRequest.getFileParams());
        rsp = WebUtils.doPost(urlSb.toString(), appParams, fileParams, connectTimeout, readTimeout);
      } else {
        rsp = WebUtils.doPost(urlSb.toString(), appParams, connectTimeout, readTimeout);
      }
    } catch (IOException e) {
      throw new ApiException(e);
    }
    result.put("rsp", rsp);
    result.put("textParams", appParams);
    result.put("protocalMustParams", proMustParams);
    result.put("protocalOptParams", proOptParams);
    result.put("url", urlSb.toString());
    return result;
  }

  public void setNeedCheckRequest(boolean needCheckRequest) {
    this.needCheckRequest = needCheckRequest;
  }

  public void setNeedEnableParser(boolean needEnableParser) {
    this.needEnableParser = needEnableParser;
  }

  public void setNeedEnableLogger(boolean needEnableLogger) {
    ApiLogger.setNeedEnableLogger(needEnableLogger);
  }

}
